package me.zhengjie.pay.task;

import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.core.http.HttpClient;
import com.wechat.pay.java.service.billdownload.BillDownloadService;
import com.wechat.pay.java.service.billdownload.model.*;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.business.service.GsglService;
import me.zhengjie.config.WechatPayConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 定时任务下载账单
 */
@Slf4j
@Async
@Component
public class DownloadTradebillTask {

    private static final Logger logger = LoggerFactory.getLogger(DownloadTradebillTask.class);
    private static final int MAX_RETRY_COUNT = 3; // 每个文件的最大重试次数
    @Autowired
    private WechatPayConfig wechatPayConfig;
    @Autowired
    private GsglService gsglService;

    @Value("${file.windows.path}")
    private String bsaeFolder;

    public void downloadTradeBill() {
        try {
            logger.info("开始下载交易账单...");

            // 获取配置
            Config config = wechatPayConfig.getNotificationConfig();
            // 创建 HttpClientAdapter 实例
            HttpClient httpClient =
                    new DefaultHttpClientBuilder()
                            .config(config)
                            .disableRetryOnConnectionFailure()
                            .enableRetryMultiDomain()
                            .build();

                // TODO 定时任务查询企业信息
            List<Map<String, String>> subMchids = gsglService.selectInfoByWxmchidIsNotNull();

            // 格式化为 yyyy-MM-dd 格式
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            // 获取昨天的日期
            LocalDate yesterday = LocalDate.now().minusDays(1);
            String billDate = yesterday.format(formatter);

            // 创建 BillDownloadService 实例
            BillDownloadService billDownloadService = new BillDownloadService.Builder().config(config).build();

            for (Map<String, String> sub : subMchids) {
                String subMchid = sub.get("WXMCHID");
                String qyName = sub.get("BIS_GSGL_QYMC");

                logger.info("开始下载子商户号: {}", subMchid);

                GetTradeBillRequest request = new GetTradeBillRequest();
                request.setBillDate(billDate);
                request.setSubMchid(subMchid); // 子商户号
                // 调用申请账单下载链接接口
                String downloadUrl = null;
                String hashValue = null;
                // 调用申请账单下载链接接口
                try {
                    QueryBillEntity response = billDownloadService.getTradeBill(request);
                    if (response == null || response.getDownloadUrl() == null) {
                        logger.error("子商户号 {} 的账单下载链接为空，无法继续下载。", subMchid);
                        return;
                    }
                    downloadUrl = response.getDownloadUrl();
                    hashValue = response.getHashValue(); // 哈希值

                    // 输出下载链接和账单摘要
                    logger.info("下载链接: {}", downloadUrl);
                    logger.info("账单摘要: {}", hashValue);
                } catch (Exception e) {
                    logger.error("调用申请账单下载链接接口时发生异常，子商户号: {}，错误信息: {}", subMchid, e.getMessage(), e);
                }


                // 动态生成文件保存路径
                String saveDirectory = bsaeFolder + yesterday.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
                String filePath = saveDirectory + "\\" + qyName + "_" + subMchid + ".xlsx";

                // 检查文件夹是否存在，不存在则创建
                File folder = new File(saveDirectory);
                if (!folder.exists()) {
                    boolean folderCreated = folder.mkdirs();
                    if (folderCreated) {
                        logger.info("文件夹不存在，已创建: {}", saveDirectory);
                    } else {
                        logger.error("无法创建文件夹: {}", saveDirectory);
                        continue;
                    }
                }

                // 以下是接下来的操作，可以使用下载链接和哈希值进行下载和校验
                if (downloadUrl != null && hashValue != null) {
                    // 下载账单文件并保存到指定路径
                    downloadBillFile(httpClient, downloadUrl, filePath);

                    // 校验文件的完整性
                    boolean isFileValid = verifyFileIntegrity(httpClient, downloadUrl, filePath, hashValue);
                    if (isFileValid) {
                        logger.info("子商户号 {} 的账单文件完整，哈希值匹配。", subMchid);
                    } else {
                        logger.error("子商户号 {} 的账单文件校验失败，哈希值不匹配！", subMchid);
                    }
                }
            }

        } catch (Exception e) {
            logger.error("下载账单时发生错误", e);
        }
    }

    /**
     * 下载账单并保存为文件
     *
     * @param httpClient      HttpClient 实例
     * @param downloadUrl     下载链接
     * @param destinationPath 文件保存路径
     * @throws IOException 如果下载或保存过程中出现错误
     */
    private void downloadBillFile(HttpClient httpClient, String downloadUrl, String destinationPath) throws IOException {
        logger.info("开始下载账单文件，下载链接: {}", downloadUrl);

        // 使用 try-with-resources 确保流被正确关闭
        try (InputStream inputStream = httpClient.download(downloadUrl);
             FileOutputStream fileOutputStream = new FileOutputStream(destinationPath)) {

            // 创建缓冲区进行读取和写入
            byte[] buffer = new byte[1024];
            int bytesRead;

            // 逐步读取并写入文件
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                fileOutputStream.write(buffer, 0, bytesRead);
            }

            logger.info("账单文件已成功下载并保存到: {}", destinationPath);

        } catch (IOException e) {
            logger.error("下载账单文件时发生错误，下载链接: {}", downloadUrl, e);
            throw e; // 重新抛出异常
        }
    }

    /**
     * 校验文件的完整性，通过比较文件的哈希值
     *
     * @param filePath     文件路径
     * @param expectedHash 期望的哈希值
     * @return 如果文件的哈希值与预期值匹配，返回 true；否则返回 false
     */
    private boolean verifyFileIntegrity(HttpClient httpClient, String downloadUrl, String filePath, String expectedHash) throws IOException {
        for (int attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++) {
            try {
                // 计算文件的 SHA-1 哈希值
                String fileHash = calculateFileHash(filePath);

                // 对比计算得到的哈希值与返回的哈希值
                if (fileHash.equals(expectedHash)) {
                    return true;  // 文件完整，哈希值匹配
                } else {
                    logger.error("哈希校验失败，重试 {}/{} 次...", attempt, MAX_RETRY_COUNT);
                }
            } catch (Exception e) {
                logger.error("计算文件哈希值时发生错误，重试 {}/{} 次...", attempt, MAX_RETRY_COUNT, e);
            }

            // 如果是最后一次重试，输出校验失败的日志，并重新下载文件
            if (attempt == MAX_RETRY_COUNT) {
                logger.error("账单文件校验失败，哈希值不匹配！重新下载并校验...");
                // 如果是最后一次，重新下载文件并重新校验
                downloadBillFile(httpClient, downloadUrl, filePath);
            }

            // 重试前的短暂延时
            try {
                Thread.sleep(2000);  // 暂停 2 秒后再重试
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return false;
    }

    /**
     * 计算文件的 SHA-1 哈希值
     *
     * @param filePath 文件路径
     * @return 计算后的 SHA-1 哈希值
     * @throws Exception 如果计算过程中发生错误
     */
    private String calculateFileHash(String filePath) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        File file = new File(filePath);

        try (InputStream fis = new java.io.FileInputStream(file)) {
            byte[] byteArray = new byte[1024];
            int bytesCount;

            while ((bytesCount = fis.read(byteArray)) != -1) {
                digest.update(byteArray, 0, bytesCount);
            }
        }

        byte[] bytes = digest.digest();
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            stringBuilder.append(String.format("%02x", b));
        }

        return stringBuilder.toString();
    }
}
